/****************************************************************
 *								*
 *	Copyright 2001, 2008 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

/* iosocket_snr.c
 * 	description:
 * 		-- takes care of the buffering of the recv for the socket device (and possible tcp device)
 * 	parameters:
 * 		-- socketptr		pointer to a socket device, where we get socket descriptor, buffer and offset in buffer
 * 		-- buffer		pointer to the buffer where to return stuff
 * 		-- maxlength		maximum number of bytes to get
 * 		-- flags		flags to be passed to recv()
 * 		-- time_for_read	pointer to the timeout structure used by select()
 * 		-- extra_status		reports either a timeout or a lost-of-connection
 * 	return:
 * 		-- got some stuff to return 					return number of bytes received
 * 		-- got nothing and timed out					return 0
 * 		-- lost-of-connection						return -2
 * 		-- error condition						return -1, with errno set
 *	side note:
 * 		-- use our own buffer if the requested size is smaller than our own buffer size
 * 		-- use the caller's buffer if the requested size is bigger than our own buffer
 * 	control flow:
 * 		-- if we already have some leftover, use it and figure out how much is still needed
 * 		-- if there is still need to read, figure out whether to use our buffer or the passed-in buffer
 * 		-- select so that this operation is timed
 * 		-- if select returns positive, recv, otherwise, return timeout
 * 		-- if recv gets nothing, return lost-of-connection
 * 		-- if the device buffer is used, move it over to the return buffer and update the device buffer pointer
 */
#include "mdef.h"
#include <sys/types.h>
#include <errno.h>
#include "gtm_stdio.h"
#include "gtm_time.h"
#include "gtm_inet.h"
#include "gtm_string.h"
#ifdef UNIX
#include "gtm_fcntl.h"
#include "eintr_wrappers.h"
static int fcntl_res;
#ifdef DEBUG
#include <sys/time.h>		/* for gettimeofday */
#endif
#ifdef GTM_USE_POLL_FOR_SUBSECOND_SELECT
#include <sys/poll.h>
#endif
#endif
#include "gt_timer.h"
#include "io.h"
#include "iotimer.h"
#include "iotcpdef.h"
#include "iotcproutine.h"
#include "stringpool.h"
#include "iosocketdef.h"
#include "min_max.h"
#include "gtm_utf8.h"
#include "outofband.h"

/* MAX_SNR_IO is for read loop in iosocket_snr_utf_prebuffer(). It is possible for a series of interrupts (one
   from each active region) to interfere with this read so be generous here.
*/
#define MAX_SNR_IO		50

GBLREF	io_pair 		io_curr_device;
GBLREF	bool			out_of_time;
GBLREF	spdesc 			stringpool;
GBLREF	tcp_library_struct	tcp_routines;
GBLREF	int4			outofband;

/* Local routine we aren't making static due to increased debugging difficult static routines make */
ssize_t iosocket_snr_io(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read);

/* Select aNd Receive */
ssize_t iosocket_snr(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read)
{
	int		status;
	ssize_t		bytesread, recvsize;
	void		*recvbuff;

	SOCKET_DEBUG2(PRINTF("socsnr: socketptr: %08lx  buffer: %08lx  maxlength: %d,  flags: %d\n",
			    socketptr, buffer, maxlength, flags); DEBUGSOCKFLUSH);
	/* ====================== use leftover from the previous read, if there is any ========================= */
	assert(maxlength > 0);
	if (socketptr->buffered_length > 0)
	{
		SOCKET_DEBUG2(PRINTF("socsnr: read from buffer - buffered_length: %d\n", socketptr->buffered_length);
			     DEBUGSOCKFLUSH);
		bytesread = MIN(socketptr->buffered_length, maxlength);
		memcpy(buffer, (void *)(socketptr->buffer + socketptr->buffered_offset), bytesread);
		socketptr->buffered_offset += bytesread;
		socketptr->buffered_length -= bytesread;
		SOCKET_DEBUG2(PRINTF("socsnr: after buffer read - buffered_offset: %d  buffered_length: %d\n",
				    socketptr->buffered_offset, socketptr->buffered_length); DEBUGSOCKFLUSH);
		return bytesread;
	}
	/* ===================== decide on which buffer to use and the size of the recv ======================== */
	if (socketptr->buffer_size > maxlength)
	{
		recvbuff = socketptr->buffer;
		recvsize = socketptr->buffer_size;
	} else
	{
		recvbuff = buffer;
		recvsize = maxlength;
	}
	VMS_ONLY(recvsize = MIN(recvsize, VMS_MAX_TCP_IO_SIZE));
	SOCKET_DEBUG2(PRINTF("socsnr: recvsize set to %d\n", recvsize); DEBUGSOCKFLUSH);

	/* =================================== select and recv ================================================= */
	assert(0 == socketptr->buffered_length);
	socketptr->buffered_length = 0;
	bytesread = (int)iosocket_snr_io(socketptr, recvbuff, recvsize, flags, time_for_read);
	SOCKET_DEBUG2(PRINTF("socsnr: bytes read from recv: %d  timeout: %d\n", bytesread, out_of_time); DEBUGSOCKFLUSH);
	if (0 < bytesread)
	{	/* -------- got something this time ----------- */
		if (recvbuff == socketptr->buffer)
		{
			if (bytesread <= maxlength)
				memcpy(buffer, socketptr->buffer, bytesread);
			else
			{	/* -------- got some stuff for future recv -------- */
				memcpy(buffer, socketptr->buffer, maxlength);
				socketptr->buffered_length = bytesread - maxlength;
				bytesread = socketptr->buffered_offset = maxlength;
				SOCKET_DEBUG2(PRINTF("socsnr: Buffer updated post read - buffered_offset: %d  "
						     "buffered_length: %d\n",
						     socketptr->buffered_offset, socketptr->buffered_length); DEBUGSOCKFLUSH);
			}
		}
	}
	return bytesread;
}

#ifdef DEBUG
/* hold gettimeofday before and after select to debug AIX spin */
static	struct timeval tvbefore, tvafter;
#endif

/* Do the IO dirty work. Note the return value can be from either select() or recv().
   This would be a static routine but that just makes it harder to debug.
*/
ssize_t iosocket_snr_io(socket_struct *socketptr, void *buffer, size_t maxlength, int flags, ABS_TIME *time_for_read)
{
	int		status, bytesread, real_errno;
	fd_set		tcp_fd;
	ABS_TIME	lcl_time_for_read;
#ifdef GTM_USE_POLL_FOR_SUBSECOND_SELECT
	long		poll_timeout;
	unsigned long	poll_nfds;
	struct pollfd	poll_fdlist[1];
#endif

	SOCKET_DEBUG2(PRINTF("socsnrio: Socket read request - socketptr: %08lx  buffer: %08lx  maxlength: %d  flags: %d  ",
			     socketptr, buffer, maxlength, flags); DEBUGSOCKFLUSH);
	SOCKET_DEBUG2(PRINTF("time_for_read->at_sec: %d  usec: %d\n", time_for_read->at_sec, time_for_read->at_usec);
		      DEBUGSOCKFLUSH);
	DEBUG_ONLY(gettimeofday(&tvbefore, NULL);)
#ifndef GTM_USE_POLL_FOR_SUBSECOND_SELECT
        FD_ZERO(&tcp_fd);
        FD_SET(socketptr->sd, &tcp_fd);
        assert(0 != FD_ISSET(socketptr->sd, &tcp_fd));
        lcl_time_for_read = *time_for_read;
        status = tcp_routines.aa_select(socketptr->sd + 1, (void *)(&tcp_fd), (void *)0, (void *)0, &lcl_time_for_read);
#else
	poll_fdlist[0].fd = socketptr->sd;
	poll_fdlist[0].events = POLLIN;
	poll_nfds = 1;
	poll_timeout = time_for_read->at_usec / 1000;	/* convert to millisecs */
	status = poll(&poll_fdlist[0], poll_nfds, poll_timeout);
#endif
	real_errno = errno;
	DEBUG_ONLY(gettimeofday(&tvafter, NULL);)
	SOCKET_DEBUG2(PRINTF("socsnrio: Select return code: %d :: errno: %d\n", status, real_errno); DEBUGSOCKFLUSH);
        if (0 < status)
	{
                bytesread = tcp_routines.aa_recv(socketptr->sd, buffer, maxlength, flags);
		real_errno = errno;
		SOCKET_DEBUG2(PRINTF("socsnrio: aa_recv return code: %d :: errno: %d\n", bytesread, errno); DEBUGSOCKFLUSH);
		if ((0 == bytesread) ||
		    ((-1 == bytesread) && (ECONNRESET == real_errno || EPIPE == real_errno || EINVAL == real_errno)))
                {       /* ----- lost connection ------- */
                        if (0 == bytesread)
                                errno = ECONNRESET;
                        return (ssize_t)(-2);
                }
		SOCKET_DEBUG2(errno = real_errno);
                return bytesread;
	}
	SOCKET_DEBUG2(errno = real_errno);
	return (ssize_t)status;
}

/* When scanning for delimiters, we have to make sure that the next read can pull in at least one full utf char.
   Failure to do this means that if a partial utf8 char is read, it will be rebuffered, reread, rebuffered, forever.
   A return code of zero indicates a timeout error occured. A negative return code indicates an IO error of some sort.
   A positive return code is the length in bytes of the next unicode char in the buffer.
*/
ssize_t iosocket_snr_utf_prebuffer(io_desc *iod, socket_struct *socketptr, int flags, ABS_TIME *time_for_read,
				   boolean_t wait_for_input)
{
	int	mblen, bytesread, real_errno;
	ssize_t  readlen;
	char	*readptr;

	assert(CHSET_M != iod->ichset);
	SOCKET_DEBUG2(PRINTF("socsnrupb: Enter prebuffer: buffered_length: %d  wait_for_input: %d\n",
			    socketptr->buffered_length, wait_for_input); DEBUGSOCKFLUSH);
	/* See if there is *anything* in the buffer */
	if (0 == socketptr->buffered_length)
	{	/* Buffer is empty, read at least one char into it so we can check how many we need */
		do
		{
			bytesread = iosocket_snr_io(socketptr, socketptr->buffer, socketptr->buffer_size, flags, time_for_read);
			SOCKET_DEBUG2(real_errno = errno);
			SOCKET_DEBUG2(PRINTF("socsnrupb: Buffer empty - bytes read: %d  errno: %d\n", bytesread, real_errno);
				      DEBUGSOCKFLUSH);
			SOCKET_DEBUG2(errno = real_errno);
		} while (((-1 == bytesread && EINTR == errno) || (0 == bytesread && wait_for_input)) &&
			 !out_of_time && 0 == outofband);
		if (out_of_time || 0 != outofband)
		{
			SOCKET_DEBUG(if (out_of_time) PRINTF("socsnrupb: Returning due to timeout\n");
				     else PRINTF("socsnrupb: Returning due to outofband\n"); DEBUGSOCKFLUSH);
			if (0 < bytesread)
			{	/* If we read anything, be sure to consider it buffered */
				socketptr->buffered_length = bytesread;
				socketptr->buffered_offset = 0;
			}
			return 0;
		}
		if (0 >= bytesread)
		{
			SOCKET_DEBUG2(real_errno = errno);
			SOCKET_DEBUG2(PRINTF("socsnrupb: Returning due to error code %d  errno: %d\n", bytesread, real_errno);
				     DEBUGSOCKFLUSH);
			SOCKET_DEBUG2(errno = real_errno);
			return bytesread;
		}
		socketptr->buffered_length = bytesread;
		socketptr->buffered_offset = 0;
	}
	/* Compute number of bytes we need for the first char in the buffer */
	readptr = socketptr->buffer + socketptr->buffered_offset;
	switch(iod->ichset)
	{
		case CHSET_UTF8:
			mblen = UTF8_MBFOLLOW(readptr);
			if (0 > mblen)
				mblen = 0;	/* Invalid char, just assume one char needed */
			break;
		case CHSET_UTF16BE:
			mblen = UTF16BE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
			if (0 > mblen)
				mblen = 1;	/* If buffer is too small we will get -1 here. Assume need 2 chars */
			break;
		case CHSET_UTF16LE:
			mblen = UTF16LE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
                        if (0 > mblen)
                                mblen = 1;      /* If buffer is too small we will get -1 here. Assume need 2 chars */
			break;
		case CHSET_UTF16:
			/* Special case as we don't know which mode we are in. This should only be used when
			   checking for BOMs. Check if first char is 0xFF or 0xFE. If it is, return 1 as our
			   (follow) length. If neither, assume UTF16BE (default UTF16 codeset) and return the
			   length it gives.
			*/
			if (0xFF == (unsigned char)*readptr || 0xFE == (unsigned char)*readptr)
				mblen = 1;
			else
			{
				mblen = UTF16BE_MBFOLLOW(readptr, readptr + socketptr->buffered_length);
				if (0 > mblen)
					mblen = 1;      /* If buffer is too small we will get -1 here. Assume need 2 chars */
			}
			break;
		default:
			GTMASSERT;
	}
	mblen++;	/* Include first char we were looking at in the required byte length */
	SOCKET_DEBUG2(PRINTF("socsnrupb: Length of char: %d\n", mblen); DEBUGSOCKFLUSH);
	if (socketptr->buffered_length < mblen)
	{	/* Still insufficient chars in the buffer for our utf character. Read some more in. */
		if ((socketptr->buffered_offset + mblen) > socketptr->buffer_size)
		{	/* Our char won't fit in the buffer. This can only occur if the read point is
			   right at the end of the buffer since the minimum buffer size is 32K. Solution
			   is to slide the part of the char that we have down to the beginning of the
			   buffer so we have plenty of room. Since this is at most 3 bytes, this is not
			   a major performance concern.
			*/
			SOCKET_DEBUG2(PRINTF("socsnrupb: Char won't fit in buffer, slide it down\n"); DEBUGSOCKFLUSH);
			assert(sizeof(int) > socketptr->buffered_length);
			assert(socketptr->buffered_offset > socketptr->buffered_length); /* Assert no overlap */
			memcpy(socketptr->buffer, (socketptr->buffer + socketptr->buffered_offset), socketptr->buffered_length);
			socketptr->buffered_offset = 0;
		}
		while (socketptr->buffered_length < mblen)
		{
			SOCKET_DEBUG2(PRINTF("socsnrupb: Top of read loop for char - buffered_length: %d\n",
					     socketptr->buffered_length); DEBUGSOCKFLUSH);
			readptr = socketptr->buffer + socketptr->buffered_offset + socketptr->buffered_length;
			readlen = socketptr->buffer_size - socketptr->buffered_offset - socketptr->buffered_length;
			assert(0 < readlen);
			bytesread = (int)iosocket_snr_io(socketptr, readptr, readlen, flags, time_for_read);
			SOCKET_DEBUG2(PRINTF("socsnrupb: Read %d chars\n", bytesread); DEBUGSOCKFLUSH);
			if (0 > bytesread)
			{       /* Some error occurred. Check for restartable condition. */
				if (EINTR == errno)
					if (!out_of_time)
						continue;
					else
						return 0;	/* timeout indicator */
				return bytesread;
			}
			if (out_of_time)
				return 0;
			socketptr->buffered_length += bytesread;
		}
	}
	SOCKET_DEBUG2(PRINTF("socsnrupb: Returning char length %d -- buffered_length: %d\n", mblen, socketptr->buffered_length);
		      DEBUGSOCKFLUSH);
	return mblen;
}


/* Place len bytes pointed by buffer back into socketptr's internal buffer */
/* Side effect: suppose the last snr was with a length > internal buffer size, we would not have used the internal buffer. For
 * that case, unsnr might move data not in the internal buffer into the internal buffer and also might result in buffer
 * expansion
 */
void iosocket_unsnr(socket_struct *socketptr, unsigned char *buffer, size_t len)
{
	char	*new_buff;

	if (socketptr->buffered_length + len <= socketptr->buffer_size)
	{
		if (socketptr->buffered_length > 0)
		{
			if (socketptr->buffered_offset < len)
			{
				memmove(socketptr->buffer + len, socketptr->buffer + socketptr->buffered_offset,
						socketptr->buffered_length);
				memmove(socketptr->buffer, buffer, len);
			} else
			{
				memmove(socketptr->buffer, buffer, len);
				memmove(socketptr->buffer + len, socketptr->buffer + socketptr->buffered_offset,
						socketptr->buffered_length);
			}
		} else
			memmove(socketptr->buffer, buffer, len);
	} else
	{
		new_buff = malloc(socketptr->buffered_length + len);
		memcpy(new_buff, buffer, len);
		if (socketptr->buffered_length > 0)
			memcpy(new_buff + len, socketptr->buffer + socketptr->buffered_offset, socketptr->buffered_length);
		free(socketptr->buffer);
		socketptr->buffer = new_buff;
	}
	socketptr->buffered_offset = 0;
	socketptr->buffered_length += len;
	return;
}
